//
//  LayoutAnchor.swift
//  BasicTools
//
//  Created by Ting Han on 2022/3/11.
//

import Foundation
import UIKit

precedencegroup AnchorPrecedence {
    lowerThan: AdditionPrecedence
}

// active: equal, lessThanOrEqual, greaterThanOrEqual
infix operator =*=: AnchorPrecedence
infix operator <*=: AnchorPrecedence
infix operator >*=: AnchorPrecedence

// inactive
infix operator =~=: AnchorPrecedence
infix operator <~=: AnchorPrecedence
infix operator >~=: AnchorPrecedence


// MARK: constraint anchor

protocol LayoutAnchorProtocol {
    associatedtype AnchorType: AnyObject
    
    func constraint(equalTo anchor: NSLayoutAnchor<AnchorType>, constant c: CGFloat) -> NSLayoutConstraint
    
    func constraint(greaterThanOrEqualTo anchor: NSLayoutAnchor<AnchorType>, constant c: CGFloat) -> NSLayoutConstraint
    
    func constraint(lessThanOrEqualTo anchor: NSLayoutAnchor<AnchorType>, constant c: CGFloat) -> NSLayoutConstraint
}

extension NSLayoutXAxisAnchor: LayoutAnchorProtocol {}
extension NSLayoutYAxisAnchor: LayoutAnchorProtocol {}
extension NSLayoutDimension: LayoutAnchorProtocol {}

extension LayoutAnchorProtocol {
    func constraint(other: Self, constant c: CGFloat, active: Bool = true) -> NSLayoutConstraint {
        let constraint = self.constraint(equalTo: other as! NSLayoutAnchor<AnchorType>, constant: c)
        constraint.isActive = active
        return constraint
    }
    
    func constraintLess(other: Self, constant c: CGFloat, active: Bool = true) -> NSLayoutConstraint {
        let constraint = self.constraint(lessThanOrEqualTo: other as! NSLayoutAnchor<AnchorType>, constant: c)
        constraint.isActive = active
        return constraint
    }
    
    func constraintGreater(other: Self, constant c: CGFloat, active: Bool = true) -> NSLayoutConstraint {
        let constraint = self.constraint(greaterThanOrEqualTo: other as! NSLayoutAnchor<AnchorType>, constant: c)
        constraint.isActive = active
        return constraint
    }
}

extension LayoutAnchorProtocol {
    static func +(lhs: Self, rhs: CGFloat) -> (Self, CGFloat) { (lhs, rhs) }
    static func -(lhs: Self, rhs: CGFloat) -> (Self, CGFloat) { (lhs, rhs) }
    
    @discardableResult
    static func =*=(lhs: Self, rhs: Self) -> NSLayoutConstraint{
        return lhs.constraint(other: rhs, constant: 0, active: true)
    }
    
    @discardableResult
    static func =*=(lhs: Self, rhs: (Self, CGFloat)) -> NSLayoutConstraint{
        return lhs.constraint(other: rhs.0, constant: rhs.1, active: true)
    }
    
    @discardableResult
    static func <*=(lhs: Self, rhs: Self) -> NSLayoutConstraint{
        return lhs.constraintLess(other: rhs, constant: 0, active: true)
    }
    
    @discardableResult
    static func <*=(lhs: Self, rhs: (Self, CGFloat)) -> NSLayoutConstraint{
        return lhs.constraintLess(other: rhs.0, constant: rhs.1, active: true)
    }
    
    @discardableResult
    static func >*=(lhs: Self, rhs: Self) -> NSLayoutConstraint{
        return lhs.constraintGreater(other: rhs, constant: 0, active: true)
    }
    
    @discardableResult
    static func >*=(lhs: Self, rhs: (Self, CGFloat)) -> NSLayoutConstraint{
        return lhs.constraintGreater(other: rhs.0, constant: rhs.1, active: true)
    }
    
    @discardableResult
    static func =~=(lhs: Self, rhs: Self) -> NSLayoutConstraint{
        return lhs.constraint(other: rhs, constant: 0, active: false)
    }
    
    @discardableResult
    static func =~=(lhs: Self, rhs: (Self, CGFloat)) -> NSLayoutConstraint{
        return lhs.constraint(other: rhs.0, constant: rhs.1, active: false)
    }
    
    @discardableResult
    static func <~=(lhs: Self, rhs: Self) -> NSLayoutConstraint{
        return lhs.constraintLess(other: rhs, constant: 0, active: false)
    }
    
    @discardableResult
    static func <~=(lhs: Self, rhs: (Self, CGFloat)) -> NSLayoutConstraint{
        return lhs.constraintLess(other: rhs.0, constant: rhs.1, active: false)
    }
    
    @discardableResult
    static func >~=(lhs: Self, rhs: Self) -> NSLayoutConstraint{
        return lhs.constraintGreater(other: rhs, constant: 0, active: false)
    }
    
    @discardableResult
    static func >~=(lhs: Self, rhs: (Self, CGFloat)) -> NSLayoutConstraint{
        return lhs.constraintGreater(other: rhs.0, constant: rhs.1, active: false)
    }
}


// MARK: NSLayoutDimension constant

extension NSLayoutDimension {
    @discardableResult
    static func =*=(lhs: NSLayoutDimension, rhs: CGFloat) -> NSLayoutConstraint {
        let constraint = lhs.constraint(equalToConstant: rhs)
        constraint.isActive = true
        return constraint
    }
    
    @discardableResult
    static func <*=(lhs: NSLayoutDimension, rhs: CGFloat) -> NSLayoutConstraint {
        let constraint = lhs.constraint(lessThanOrEqualToConstant: rhs)
        constraint.isActive = true
        return constraint
    }
    
    @discardableResult
    static func >*=(lhs: NSLayoutDimension, rhs: CGFloat) -> NSLayoutConstraint {
        let constraint = lhs.constraint(greaterThanOrEqualToConstant: rhs)
        constraint.isActive = true
        return constraint
    }
    
    @discardableResult
    static func =~=(lhs: NSLayoutDimension, rhs: CGFloat) -> NSLayoutConstraint {
        let constraint = lhs.constraint(equalToConstant: rhs)
        constraint.isActive = false
        return constraint
    }
    
    @discardableResult
    static func <~=(lhs: NSLayoutDimension, rhs: CGFloat) -> NSLayoutConstraint {
        let constraint = lhs.constraint(lessThanOrEqualToConstant: rhs)
        constraint.isActive = false
        return constraint
    }
    
    @discardableResult
    static func >~=(lhs: NSLayoutDimension, rhs: CGFloat) -> NSLayoutConstraint {
        let constraint = lhs.constraint(greaterThanOrEqualToConstant: rhs)
        constraint.isActive = false
        return constraint
    }
}

// MARK: NSLayoutDimension info (* & /)

struct LayoutDimensionInfo {
    let anchor: NSLayoutDimension
    let multiplier: CGFloat
    let constant: CGFloat
}

extension NSLayoutDimension {
    static func *(lhs: NSLayoutDimension, rhs: CGFloat) -> LayoutDimensionInfo {
        return LayoutDimensionInfo(anchor: lhs, multiplier: rhs, constant: 0)
    }
    static func /(lhs: NSLayoutDimension, rhs: CGFloat) -> LayoutDimensionInfo {
        return LayoutDimensionInfo(anchor: lhs, multiplier: 1/rhs, constant: 0)
    }
    static func *(lhs: CGFloat, rhs: NSLayoutDimension) -> LayoutDimensionInfo {
        return LayoutDimensionInfo(anchor: rhs, multiplier: lhs, constant: 0)
    }
    static func /(lhs: CGFloat, rhs: NSLayoutDimension) -> LayoutDimensionInfo {
        return LayoutDimensionInfo(anchor: rhs, multiplier: 1/lhs, constant: 0)
    }
}

func +(lhs: LayoutDimensionInfo, rhs: CGFloat) -> LayoutDimensionInfo {
    return LayoutDimensionInfo(anchor: lhs.anchor, multiplier: lhs.multiplier, constant: lhs.constant + rhs)
}

func -(lhs: LayoutDimensionInfo, rhs: CGFloat) -> LayoutDimensionInfo {
    return LayoutDimensionInfo(anchor: lhs.anchor, multiplier: lhs.multiplier, constant: lhs.constant - rhs)
}

extension NSLayoutDimension {
    
    @discardableResult
    static func =*= (lhs: NSLayoutDimension, rhs: LayoutDimensionInfo) -> NSLayoutConstraint {
        let constraint = lhs.constraint(equalTo: rhs.anchor, multiplier: rhs.multiplier, constant: rhs.constant)
        constraint.isActive = true
        return constraint
    }
    
    @discardableResult
    static func <*= (lhs: NSLayoutDimension, rhs: LayoutDimensionInfo) -> NSLayoutConstraint {
        let constraint = lhs.constraint(lessThanOrEqualTo: rhs.anchor, multiplier: rhs.multiplier, constant: rhs.constant)
        constraint.isActive = true
        return constraint
    }
    
    @discardableResult
    static func >*= (lhs: NSLayoutDimension, rhs: LayoutDimensionInfo) -> NSLayoutConstraint {
        let constraint = lhs.constraint(greaterThanOrEqualTo: rhs.anchor, multiplier: rhs.multiplier, constant: rhs.constant)
        constraint.isActive = true
        return constraint
    }
    
    @discardableResult
    static func =~= (lhs: NSLayoutDimension, rhs: LayoutDimensionInfo) -> NSLayoutConstraint {
        let constraint = lhs.constraint(equalTo: rhs.anchor, multiplier: rhs.multiplier, constant: rhs.constant)
        constraint.isActive = false
        return constraint
    }
    
    @discardableResult
    static func <~= (lhs: NSLayoutDimension, rhs: LayoutDimensionInfo) -> NSLayoutConstraint {
        let constraint = lhs.constraint(lessThanOrEqualTo: rhs.anchor, multiplier: rhs.multiplier, constant: rhs.constant)
        constraint.isActive = false
        return constraint
    }
    
    @discardableResult
    static func >~= (lhs: NSLayoutDimension, rhs: LayoutDimensionInfo) -> NSLayoutConstraint {
        let constraint = lhs.constraint(greaterThanOrEqualTo: rhs.anchor, multiplier: rhs.multiplier, constant: rhs.constant)
        constraint.isActive = false
        return constraint
    }
}


/*
 example:
 
 red.leadingAnchor =*= view.leadingAnchor + 100
 red.topAnchor =*= view.topAnchor + 100
 red.widthAnchor =*= 250
 red.heightAnchor =*= 300
 green.topAnchor =*= red.bottomAnchor + 20
 green.centerXAnchor =*= red.centerXAnchor
 green.widthAnchor =*= red.widthAnchor/2
 green.heightAnchor =*= green.widthAnchor*2 - 50
 
 */
